package org.bit.tcp;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

/**
 * 基于 TPC 协议的回显服务器
 *
 */
public class TCPEchoServer {
    // 声明用于服务端的类
    private ServerSocket socket;

    /**
     * 构造方法
     * @param port 服务端的端口号
     */
    public TCPEchoServer (int port) throws IOException {
        // 校验端口号
        if (port < 1025 || port > 65535) {
            throw new BindException("端口号必须在 1025 ~ 65535 之间");
        }
        // 根据指定的端口号初始化 socket 服务
        this.socket = new ServerSocket(port);
    }

    /**
     * 开始处理客户端的链接请求
     */
    public void start() throws IOException {
        System.out.println("服务器已启动，等待客户端连接.");
        // 循环处理连接请求
        while (true) {
            // 接受客户端请求，如果没有客户端连接就阻塞等待
            Socket clientSocket = socket.accept();
            // 接收到请求之后就交给专门的客户端处理
            processConnections(clientSocket);
        }
    }

    // 为了方便维护，专门把数据处理的逻辑抽取成一个方法
    private void processConnections(Socket clientSocket) {
        // 打印连接日志
        System.out.printf("[%s:%d] 客户端上线了.\n",clientSocket.getInetAddress().toString(), clientSocket.getPort());
        // 所有的通信数据都封装在 Socket 对象中，对应的就是形参clientSocket，TCP传输时字节流
        // 1. 通过输入输出流来获取和发送数据
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket .getOutputStream()) {
            // 2. 循环读取客户端发来的数据
            while (true) {
                // 通过Scanner简化文本请求的读取过程
                Scanner scanner = new Scanner(inputStream);
                // 3. 判断是否有数据
                if (!scanner.hasNextLine()) {
                    System.out.printf("[%s:%d] 客户端下线了.\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                    break;
                }
                // 4. 获取用户发来的具体内容
                String request = scanner.nextLine();
                // 5. 根据请求来计算响应
                String response = process(request);
                // 6. 把相应写入输出流
                // 6.1 通过PrintWriter简化写入操作
                PrintWriter writer = new PrintWriter(outputStream);
                // 6.2 向输出流中写入响应
                writer.println(response);
                // 7. 强制刷新缓冲区，相当于把系统数据交给了网卡，也相当于程序把数据发送出去了
                writer.flush();
                // 8. 打印日志
                System.out.printf("[%s:%d] request = %s, response = %s\n",
                        clientSocket.getInetAddress().toString(),clientSocket.getPort(), request, response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    // 处理响应的方式
    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TCPEchoServer sever = new TCPEchoServer(9999);
        sever.start();
    }
}
